/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.icpoints;

import static br.ufpe.cin.imlm2.jbeat.soot.icpoints.StmtLabel.Rethrow;

import java.util.*;

import soot.*;
import soot.jimple.AssignStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.Stmt;
import soot.jimple.ThrowStmt;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.CombinedAnalysis;
import soot.toolkits.scalar.CombinedDUAnalysis;
import soot.toolkits.scalar.UnitValueBoxPair;

import br.ufpe.cin.imlm2.jbeat.soot.common.MethodTagger;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.tags.InterconnectingPointsTag;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.tags.StmtLabelsTag;

public class TrapInspector extends MethodTagger {

	private boolean rethrows(Trap t, Body b) {
		Stmt handlerUnit = (Stmt) t.getHandlerUnit();
		List<Stmt> stmts = this.labelStmts(handlerUnit, b);
		if (stmts.size() > 0) {
			boolean rethrows = false;
			for (Stmt stmt : stmts) {
				if (stmt.hasTag(StmtLabelsTag.TAG_NAME)) {
					StmtLabelsTag slt = (StmtLabelsTag) stmt
							.getTag(StmtLabelsTag.TAG_NAME);
					if (slt.containsLabel(Rethrow))
						rethrows = true;
					slt.clearLabels();
				}
			}
			return rethrows;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	private List<Stmt> labelStmts(Stmt handlerUnit, Body b) {
		UnitGraph ug = new ExceptionalUnitGraph(b);
		CombinedAnalysis combinedAnalysis = CombinedDUAnalysis.v(ug);
		Queue<UnitValueBoxPair> workList = new LinkedList<UnitValueBoxPair>(
				combinedAnalysis.getUsesOf(handlerUnit));
		List<UnitValueBoxPair> processedItems = new ArrayList<UnitValueBoxPair>();
		while (!workList.isEmpty()) {
			UnitValueBoxPair workItem = workList.remove();
			processedItems.add(workItem);
			Stmt stmt = (Stmt) workItem.getUnit();
			List<UnitValueBoxPair> uses = combinedAnalysis.getUsesOf(stmt);
			for (UnitValueBoxPair use : uses) {

				if (use.getValueBox().getValue() instanceof Local) {
					if (!processedItems.contains(use)) {
						workList.add(use);
					}
				}

			}
			StmtLabelsTag slt = (StmtLabelsTag) stmt
					.getTag(StmtLabelsTag.TAG_NAME);
			if (stmt.containsInvokeExpr()) {
				slt.addLabel(StmtLabel.Call_Other_Method);
			}
			if (stmt instanceof ReturnStmt) {
				slt.addLabel(StmtLabel.Exception_Object_Returned);
			} else if (stmt instanceof AssignStmt
					&& (stmt.containsFieldRef() || stmt.containsArrayRef())) {
				slt.addLabel(StmtLabel.Array_Field_Store);
			} else if (stmt instanceof ThrowStmt) {
				slt.addLabel(StmtLabel.Rethrow);
			}
		}

		List<Stmt> toBeLabeled = new ArrayList<Stmt>();
		for (UnitValueBoxPair unitValueBoxPair : processedItems) {
			toBeLabeled.add((Stmt) unitValueBoxPair.getUnit());
		}

		return toBeLabeled;
	}

	protected void checkTags(SootMethod meth) {
		if (meth.isConcrete()) {
			if (!meth.retrieveActiveBody().hasTag(
					InterconnectingPointsTag.TAG_NAME)) {
				meth.getActiveBody().addTag(new InterconnectingPointsTag());
			}
		}
	}

	@Override
	protected void tag(Collection<SootClass> classes) {
		System.err.println("Trap inspection beginning");
		Long startTime = System.currentTimeMillis();
		for (SootClass sootClass : classes) {
			List<SootMethod> meths = sootClass.getMethods();
			this.filterOutMethods(meths);
			for (SootMethod sootMethod : meths) {
				Body b = sootMethod.retrieveActiveBody();
				this.addStmtLabelsTags(b);
				for (Trap t : b.getTraps()) {
					if (this.rethrows(t, b)) {
						InterconnectingPointsTag ipt = (InterconnectingPointsTag) sootMethod
								.retrieveActiveBody().getTag(
										InterconnectingPointsTag.TAG_NAME);
						ipt.addInterconnectingPoint(t);
					}
				}
				this.removeStmtLabelsTags(b);
			}
		}
		System.err.println("Trap inspection finished in "
				+ (System.currentTimeMillis() - startTime) + "ms");
	}

	private void filterOutMethods(Iterable<SootMethod> meths) {
		Iterator<SootMethod> it = meths.iterator();
		while (it.hasNext()) {
			if (!it.next().isConcrete()) {
				it.remove();
			}
		}

	}

	private void addStmtLabelsTags(Body b) {
		for (Unit t : b.getUnits()) {
			t.addTag(new StmtLabelsTag());
		}
	}

	private void removeStmtLabelsTags(Body b) {
		for (Unit u : b.getUnits()) {
			u.removeTag(StmtLabelsTag.TAG_NAME);
		}
	}

}
